home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
pcl
/
cl-nd-cl.lha
/
clue
/
clio
/
table.lisp
< prev
next >
Wrap
Text File
|
1991-07-15
|
63KB
|
1,639 lines
;;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Syntax:Common-Lisp -*-
;;;----------------------------------------------------------------------------------+
;;; |
;;; TEXAS INSTRUMENTS INCORPORATED |
;;; P.O. BOX 149149 |
;;; AUSTIN, TEXAS 78714 |
;;; |
;;; Copyright (C) 1989, 1990 Texas Instruments Incorporated. |
;;; |
;;; Permission is granted to any individual or institution to use, copy, modify, and |
;;; distribute this software, provided that this complete copyright and permission |
;;; notice is maintained, intact, in all copies and supporting documentation. |
;;; |
;;; Texas Instruments Incorporated provides this software "as is" without express or |
;;; implied warranty. |
;;; |
;;;----------------------------------------------------------------------------------+
(in-package "CLIO-OPEN")
(export '(
table
make-table
table-column-alignment
table-column-width
table-columns
table-delete-policy
table-layout-size-policy
table-member
table-row-alignment
table-row-height
table-same-height-in-row
table-same-width-in-column
table-separator
table-row
table-column
)
'clio-open)
;;;
;;; Call-Tree...
;;;
;;; Preferred-Size (Table)
;;; . check-for-existing-wis
;;; . place-children-physically
;;; . . put-kids-into-maximum-unaligned-columns
;;; . . . find-first-parents-width
;;; . . . assign-kids-to-rows-and-columns
;;; . . . preferred-size (child)
;;; . . . move (child)
;;; . . . resize (child)
;;; . . put-kids-into-maximum-aligned-columns
;;; . . . assign-kids-to-rows-and-columns
;;; . . . . assign-a-kid-to-a-row-and-column
;;; . . . . build-sorted-list-of-children
;;; . . . get-maximum-possible-ncolumns
;;; . . . . preferred-size (child)
;;; . . . preferred-size (child)
;;; . . . adjust-column-widths-so-child-fits
;;; . . put-kids-into-specified-number-of-columns
;;; . . . assign-kids-to-rows-and-columns
;;; . . . preferred-size (child)
;;; . . scan-for-largest-children
;;; . . . preferred-size (child)
;;; . . determine-a-rows-height
;;; . . preferred-size (child)
;;; . . move (child)
;;; . . resize (child)
;;; . . calculate-preferred-height
;;; . . determine-a-rows-height
;;; . . . preferred-size (child)
;;; . . calculate-preferred-width
;;; .
;;; Change-Layout(Table)
;;; . check-for-existing-wis
;;; . place-children-physically
;;; . change-geometry (Table)
;;; .
;;; Resize :after (Table)
;;; . change-layout (Table)
;;; .
;;; Manage-Geometry (Table)
;;; . Change-Geometry (Table)
;;; Basic Organization and Flow:
;;; The Table contact lays out its children per the values of its policy resources and the
;;; row/column constraints of its children, with the resource values always taking precedence
;;; over the children's constraint values.
;;;
;;; The function place-children-physically does the real work of Table.
;;;
;;; The differences in Table's logical flow for the possible values for the :columns resource
;;; are embodied primarily in the three routines
;;;
;;; put-kids-into-maximum-unaligned-columns
;;; put-kids-into-maximum-aligned-columns
;;; put-kids-into-specified-number-of-columns
;;;
;;; There are 5 ways into the Table contact's logic:
;;;
;;; Preferred-Size (Table)
;;; Change-Layout (Table)
;;; Resize :after (Table)
;;; Manage-Geometry (Table)
;;; (SETF layout-policy-resource)
;;;
;;; ===========================================================================
;;; T h e T A B L E L a y o u t C o n t a c t
;;; ===========================================================================
(DEFCONTACT table (gravity-mixin spacing-mixin core composite)
((column-alignment :type (MEMBER :left :center :right)
:reader table-column-alignment ; SETF method defined below.
:initarg :column-alignment
:initform :left)
(column-width :type (OR (MEMBER :maximum) cons (integer 1 *))
:reader table-column-width ; SETF method defined below.
:initarg :column-width
:initform :maximum)
(columns :type (OR (integer 1 *) (MEMBER :maximum :none))
:reader table-columns ; SETF method defined below.
:initarg :columns
:initform :maximum)
(delete-policy :type (MEMBER :shrink-list :shrink-column :shrink-none :shrink-row)
:reader table-delete-policy ; SETF method defined below.
:initarg :delete-policy
:initform :shrink-none)
(layout-size-policy :type (MEMBER :maximum :minimum :none)
:reader table-layout-size-policy ; SETF method defined below.
:initarg layout-size-policy
:initform :maximum)
(row-height :type (OR (MEMBER :maximum) cons (integer 1 *))
:reader table-row-height ; SETF method defined below.
:initarg :row-height
:initform :maximum)
(row-alignment :type (MEMBER :top :center :bottom)
:reader table-row-alignment ; SETF method defined below.
:initarg :row-alignment
:initform :bottom)
(same-height-in-row
:type (MEMBER :on :off)
:reader table-same-height-in-row ; SETF method defined below.
:initarg :same-height-in-row
:initform :off)
(same-width-in-column
:type (MEMBER :on :off)
:reader table-same-width-in-column ; SETF method defined below.
:initarg :same-width-in-column
:initform :off)
(separators :type list
:initarg :separators
:initform nil))
(:resources
(border-width :initform 0)
column-alignment
column-width
columns
delete-policy
layout-size-policy
row-alignment
row-height
same-height-in-row
same-width-in-column
separators)
(:constraints
(row :type (integer 0 *))
(column :type (integer 0 *)))
(:documentation
"Arranges its children in an array of rows and columns."
))
(DEFUN make-table (&rest initargs &key &allow-other-keys)
(APPLY #'make-contact 'table initargs))
;;; =========================================================================== ;;;
;;; ORG-ENTRY: the entries on the what-if-organization list ;;;
;;; =========================================================================== ;;;
(DEFSTRUCT (org-entry :named (:type vector) (:conc-name "ORG-ENTRY-"))
kid
row
column
width
height
border-width)
(DEFUN establish-org-entry (kid row column)
(MULTIPLE-VALUE-BIND (p-w p-h p-b-w)
(preferred-size kid)
(make-org-entry :kid kid :row row :column column
:width p-w :height p-h :border-width p-b-w)))
;;; =========================================================================== ;;;
;;; What-if Structures and Their management ;;;
;;; =========================================================================== ;;;
;;;
;;; Structures of this kind are placed on the Table's plist under the :what-if-structures
;;; property to record already-performed preferred-size calculations for the current set of
;;; policy resource values but different widths/heights. Any change to a policy resource
;;; destroys this cache of what-if structures, as does a call to change-layout.
;;;
;;; Hmmmm... We must keep the children's sizes here, have all the layout logic look here
;;; rather than at the kids' preferred-size methods. Where to keep this info? In organization
;;; (which is already a list of the kids) or in another list of kids, widths, heights, and
;;; border-widths. Or in an array...
(DEFSTRUCT (what-if-structure :named (:type vector) (:conc-name "WHAT-IF-"))
width
height
border-width
organization ; org-entrys for :mapped children only!
column-widths
nrows
ncolumns
(preferred-width 0)
(preferred-height 0)
in-use
)
(DEFUN check-for-existing-wis (table width height border-width &optional dont-create-p)
;; Returns the first (newest) wis found with width/height.
;; If no wis satisfying width/height exists, create a new one unless DONT-CREATE-P
;; is true, in which case return NIL.
(DECLARE (VALUES (OR what-if-structure NULL)))
(LET ((old-wis-list (GETF (window-plist table) :what-if-structures)) wis)
(SETF wis (FIND-IF #'(lambda (wis)
(AND (EQL (what-if-width wis) width)
(EQL (what-if-height wis) height)
(EQL (what-if-border-width wis) border-width)))
old-wis-list))
(UNLESS (OR wis dont-create-p)
(SETF (GETF (window-plist table) :what-if-structures)
(PUSH (SETF wis (make-what-if-structure :width width
:height height
:border-width border-width
:preferred-width 0
:preferred-height 0))
old-wis-list))
)
wis))
;;; =========================================================================== ;;;
;;; A Table's Constraint's Accessors ;;;
;;; =========================================================================== ;;;
(defun table-row (member)
(declare (values (or null (integer 0 *))))
(contact-constraint member :row))
(defsetf table-row setf-table-row)
(defun setf-table-row (member row)
(check-type row (or null (integer 0 *)))
(setf (contact-constraint member :row) row))
(defun table-column (member)
(declare (values (or null (integer 0 *))))
(contact-constraint member :column))
(defsetf table-column setf-table-column)
(defun setf-table-column (member column)
(check-type column (or null (integer 0 *)))
(setf (contact-constraint member :column) column))
;;; =========================================================================== ;;;
;;; SETF functions for a Table's Resources ;;;
;;; =========================================================================== ;;;
(defmethod (setf display-left-margin) :after (new-value (table table))
(declare (ignore new-value))
(change-layout table))
(defmethod (setf display-right-margin) :after (new-value (table table))
(declare (ignore new-value))
(change-layout table))
(defmethod (setf display-top-margin) :after (new-value (table table))
(declare (ignore new-value))
(change-layout table))
(defmethod (setf display-bottom-margin) :after (new-value (table table))
(declare (ignore new-value))
(change-layout table))
(defun force-relayout (table)
(SETF (GETF (window-plist table) :what-if-structures) nil)
(change-layout table))
(DEFMETHOD (SETF display-horizontal-space) :after (new-value (table table))
(DECLARE (IGNORE new-value))
(force-relayout table))
(DEFMETHOD (SETF table-column-alignment) (new-value (table table))
(with-slots (column-alignment)
table
(SETF column-alignment new-value)
(force-relayout table)
new-value))
(DEFMETHOD (SETF table-column-width) (new-value (table table))
(with-slots (column-width)
table
(SETF column-width new-value)
(force-relayout table)
new-value))
(DEFMETHOD (SETF table-columns) (new-value (table table))
(with-slots (columns)
table
(SETF columns new-value)
(DOLIST (kid (composite-children table))
(SETF (table-column kid) nil
(table-row kid) nil))
(force-relayout table)
new-value))
(DEFMETHOD (SETF table-delete-policy) (new-value (table table))
(with-slots (delete-policy)
table
(SETF delete-policy new-value)
(force-relayout table)
new-value))
(DEFMETHOD (SETF table-layout-size-policy) (new-value (table table))
(with-slots (layout-size-policy)
table
(SETF layout-size-policy new-value)
(force-relayout table)
new-value))
(DEFMETHOD (SETF table-row-height) (new-value (table table))
(with-slots (row-height)
table
(SETF row-height new-value)
(force-relayout table)
new-value))
(DEFMETHOD (SETF table-row-alignment) (new-value (table table))
(with-slots (row-alignment)
table
(SETF row-alignment new-value)
(force-relayout table)
new-value))
(DEFMETHOD (SETF table-same-width-in-column) (new-value (table table))
(CHECK-TYPE new-value (MEMBER :on :off))
(with-slots (same-width-in-column)
table
(SETF same-width-in-column new-value)
(force-relayout table)
new-value))
(DEFMETHOD (SETF table-same-height-in-row) (new-value (table table))
(CHECK-TYPE new-value (MEMBER :on :off))
(with-slots (same-height-in-row)
table
(SETF same-height-in-row new-value)
(force-relayout table)
new-value))
;;; =========================================================================== ;;;
;;; A Table's Separator Methods ;;;
;;; =========================================================================== ;;;
;;; Note: The physical size of an OL UI separator (white-space) will be defined
;;; to be half the height of the row it follows.
(DEFMETHOD table-separator ((table table) row-number)
(DECLARE (type integer row-number)
(VALUES (MEMBER :on :off)))
(check-type row-number (integer 0 *))
(with-slots (separators)
table
(IF (MEMBER row-number separators) :on :off)))
(DEFMETHOD (SETF table-separator) (on-or-off (table table) row-number)
(DECLARE (type integer row-number)
(VALUES (MEMBER :on :off)))
(check-type row-number (integer 0 *))
(with-slots (separators)
table
(LET ((already-there-p (MEMBER row-number separators)))
(ECASE on-or-off
(:on (UNLESS already-there-p
(PUSH row-number separators)
(force-relayout table)))
(:off (WHEN already-there-p
(SETF separators (DELETE row-number separators))
(force-relayout table))))))
on-or-off)
;;; =========================================================================== ;;;
;;; A Table's Table-Member Method ;;;
;;; =========================================================================== ;;;
(DEFMETHOD table-member ((table table) row column)
;; Return NIL if there is no child at position row/column.
(DECLARE (VALUES (OR contact NULL)))
(LET ((wis (check-for-existing-wis table (contact-width table) (contact-height table)
(contact-border-width table))))
(WHEN wis
(org-entry-kid (FIND-IF #'(lambda (x)
(AND (= (org-entry-row x) row)
(= (org-entry-column x) column)))
(REST (what-if-organization wis)))))))
(DEFMETHOD (SETF table-member) (new-value (table table) row column)
;; What should we do with the child currently at position row/column?
;; Set its constraints to NIL? Set just one of its constraints to NIL?
;; Error if there's one there? I've chosen to blast its constraints.
(LET ((existing-child-at-that-position (table-member table row column)))
(WHEN existing-child-at-that-position
(SETF (table-row existing-child-at-that-position) nil
(table-column existing-child-at-that-position) nil))
(SETF (table-row new-value) row)
(SETF (table-column new-value) column)
(force-relayout table)
new-value))
;;; =========================================================================== ;;;
;;; A Table's Preferred-Size Method ;;;
;;; =========================================================================== ;;;
(DEFMETHOD preferred-size ((table table) &key width height border-width)
;;
;; Handle the case where we have no children...
;;
(with-slots (children) table
(UNLESS children
(RETURN-FROM preferred-size
(VALUES (+ (display-left-margin table) (display-right-margin table))
(+ (display-top-margin table) (display-bottom-margin table))
(contact-border-width table)))))
(with-slots ((old-width width) (old-height height) (old-border-width border-width)) table
;;
;; When the caller specifies no what-if values and we have a good width & height, always
;; return our current values...
;;
(WHEN (AND (NULL width) (NULL height) (/= 0 old-width) (/= 0 old-height))
(RETURN-FROM preferred-size (VALUES old-width old-height old-border-width)))
;;
;; We need to what-if. Figure out the width, height, and border-width to use...
;;
(SETF width (OR width old-width)
height (OR height old-height)
border-width (OR border-width old-border-width))
(LET ((wis (check-for-existing-wis table width height border-width)))
(UNLESS (AND (what-if-organization wis)
(= (what-if-preferred-width wis) width)
(= (what-if-preferred-height wis) height))
(place-children-physically table wis nil))
(VALUES (what-if-preferred-width wis)
(what-if-preferred-height wis)
border-width))))
;;; =========================================================================== ;;;
;;; A Table's Change-Layout Method ;;;
;;; =========================================================================== ;;;
(DEFMETHOD change-layout ((table table) &optional newly-managed)
(declare (type (or null contact) newly-managed))
(DECLARE (SPECIAL *called-from-resize-method*))
(with-slots (width height border-width) table
;; Just update the current wis if a single child is being withdrawn...
(when (AND newly-managed (EQ (contact-state newly-managed) :withdrawn))
(LET ((wis (check-for-existing-wis table width height border-width)))
(WHEN wis
(SETF (REST (what-if-organization wis))
(DELETE newly-managed (REST (what-if-organization wis))
:key #'org-entry-kid)))))
(LET (p-width p-height
(wis (check-for-existing-wis table width height border-width)))
;; With a change in layout we must really re-layout our children...
(unless (what-if-in-use wis)
(SETF (what-if-in-use wis) t)
(place-children-physically table wis t)
;;
;; Update the children's row/column constraints...
;;
(DOLIST (o-e (REST (what-if-organization wis)))
(SETF (table-row (org-entry-kid o-e)) (org-entry-row o-e)
(table-column (org-entry-kid o-e)) (org-entry-column o-e)))
(UNLESS (AND (BOUNDP '*called-from-resize-method*) *called-from-resize-method*)
(SETF p-width (what-if-preferred-width wis)
p-height (what-if-preferred-height wis))
(UNLESS (AND (= height p-height) (= width p-width))
(SETF (what-if-width wis) p-width
(what-if-height wis) p-height)
(change-geometry table :width p-width :height p-height :accept-p t)))
(SETF (what-if-in-use wis) nil)))))
;;; =========================================================================== ;;;
;;; A Table's Resize :after Method ;;;
;;; =========================================================================== ;;;
(DEFMETHOD resize :after ((table table) width height b-width)
(DECLARE (IGNORE width height b-width))
(LET ((*called-from-resize-method* t))
(DECLARE (SPECIAL *called-from-resize-method*))
(change-layout table)))
;;; =========================================================================== ;;;
;;; A Table's Manage-Geometry Method ;;;
;;; =========================================================================== ;;;
;;; This is not right yet. It should run a what-if to get a Table size for the child's
;;; size change, but this is not possible yet -- the wis doesn't keep all children's
;;; sizes. Then it must call change-geometry to see if its parent will let it be that
;;; size. If so, it should return a thunk that invokes resize, not change-geometry.
(defmethod manage-geometry ((table table) child x y width height border-width &key)
(values
(if
(or (and x (/= x (contact-x child)))
(and y (/= y (contact-y child)))
(and width (/= width (contact-width child)))
(and height (/= height (contact-height child)))
(and border-width (/= border-width (contact-border-width child))))
#'(lambda (self)
(multiple-value-bind (p-w p-h p-b-w)
(preferred-size self)
(change-geometry self
:width p-w
:height p-h
:border-width p-b-w
:accept-p t)
(change-layout self)
(display-force-output (contact-display self))))
t)
(or x (contact-x child))
(or y (contact-y child))
(or width (contact-width child))
(or height (contact-height child))
(or border-width (contact-border-width child))))
;;;
;;; Internal routines that calculate the width/height of a table, given a What-if-Structure...
;;; Calculate-Preferred-Width
;;; Calculate-Preferred-Height
(DEFUN calculate-preferred-width (table wis)
(LET* ((ncolumns (what-if-ncolumns wis))
(column-widths (what-if-column-widths wis))
(table-width (+ (display-left-margin table)
(display-right-margin table)
(* (1- ncolumns) (display-horizontal-space table)))))
(DOTIMES (column ncolumns)
(INCF table-width (AREF column-widths column 0)))
table-width))
(DEFUN calculate-preferred-height (table wis)
(with-slots (row-height separators) (THE table table)
(LET* ((nrows (what-if-nrows wis))
(organization (what-if-organization wis))
(table-height (+ (display-top-margin table)
(display-bottom-margin table)
(* (1- nrows) (display-vertical-space table))))
(org-list (REST organization))
(fixed-row-heights row-height) height-for-this-row)
(DO ((row 0 (1+ row)))
((= row nrows))
(MULTIPLE-VALUE-SETQ (height-for-this-row fixed-row-heights org-list)
(determine-a-rows-height row fixed-row-heights org-list))
(INCF table-height height-for-this-row)
;; Note: The physical size of an OL UI separator (white-space) will be defined
;; to be half the height of the row it follows. A separator placed after
;; the last row will result in extra white-space at the bottom of the table.
(WHEN (MEMBER row separators)
(INCF table-height (FLOOR (+ height-for-this-row (display-vertical-space table)) 2))))
table-height)))
(DEFUN determine-a-rows-height (row fixed-row-heights org-list1)
(LET (fixed-height-for-this-row (height-for-this-row 0) found-a-kid-in-this-row-p)
(TYPECASE fixed-row-heights
(integer
(SETF fixed-height-for-this-row fixed-row-heights))
(cons
(SETF fixed-height-for-this-row (FIRST fixed-row-heights))
(SETF fixed-row-heights (REST fixed-row-heights))))
(IF fixed-height-for-this-row
(SETF height-for-this-row fixed-height-for-this-row)
;;else find the tallest element and the largest border width in this row...
(progn
(DO ((org-list1 org-list1 (REST org-list1))
kid1 org-entry1 (kid1s-row row))
((OR (NULL org-list1) (AND found-a-kid-in-this-row-p (/= row kid1s-row))))
(SETF org-entry1 (FIRST org-list1))
(SETF kid1 (org-entry-kid org-entry1)
kid1s-row (org-entry-row org-entry1))
(WHEN (= row kid1s-row)
(SETF found-a-kid-in-this-row-p t)
(SETF height-for-this-row
(MAX height-for-this-row
(+ (org-entry-height org-entry1)
(org-entry-border-width org-entry1)
(org-entry-border-width org-entry1))))))))
;;
;; Because all the members of a row may be withdrawn (and therefore not on the
;; what-if-organization list) it is quite possible to find no children in a row. For now
;; such a row collapses to zero-height...
(VALUES height-for-this-row fixed-row-heights org-list1)))
;;; =========================================================================== ;;;
;;; The Guts of Table: Place-Children-Physically ;;;
;;; =========================================================================== ;;;
(DEFUN place-children-physically (table wis really-p)
(with-slots (children same-width-in-column same-height-in-row columns
column-alignment row-alignment
column-width row-height
separators) (THE table table)
(LET (kid last-kid-processed height-for-this-row x1 y1
(fixed-row-heights (UNLESS (EQ row-height :maximum) row-height))
fixed-column-widths
width-for-this-column
childs-horizontal-size ; Including border-widths.
childs-vertical-size ; Including border-widths.
max-child-heights-by-row
max-child-widths-by-columns
org-entry kids-row kids-column
y)
(UNLESS children
(RETURN-FROM place-children-physically))
(CASE columns
(:none
(put-kids-into-maximum-unaligned-columns table wis really-p)
(RETURN-FROM place-children-physically))
(:maximum
;; XtNmaximumColumns.
;; Must scan the kids to figure out what width each column should be.
(put-kids-into-maximum-aligned-columns table wis))
(otherwise
(UNLESS (INTEGERP columns)
(ERROR "~s is not a legal value for :columns" columns))
;; XtNrequestedColumns.
(put-kids-into-specified-number-of-columns table wis)))
;;
;; Position the children on the test sheet per the columnarization...
;;
(WHEN really-p
(MULTIPLE-VALUE-SETQ (max-child-heights-by-row max-child-widths-by-columns)
(scan-for-largest-children wis))
(LET ((org-list (REST (what-if-organization wis)))
(column-widths (what-if-column-widths wis)))
(SETF y (display-top-margin table))
(CATCH 'out-of-kids
(DOTIMES (row (what-if-nrows wis))
(SETF fixed-column-widths (UNLESS (EQ column-width :maximum) column-width))
(MULTIPLE-VALUE-SETQ (height-for-this-row fixed-row-heights)
(determine-a-rows-height row fixed-row-heights org-list))
(LET ((fixed-width-for-this-column
(AND (INTEGERP fixed-column-widths) fixed-column-widths))
(x (display-left-margin table)))
;; Now set the row's elements' geometries...
(DOTIMES (column (what-if-ncolumns wis))
(WHEN (EQ kid last-kid-processed)
(SETF org-entry (FIRST org-list))
(WHEN (NULL org-entry)
(THROW 'out-of-kids t))
(SETF kid (org-entry-kid org-entry)
kids-row (org-entry-row org-entry)
kids-column (org-entry-column org-entry)))
;; Figure out what width WE want this column to be...
(WHEN (CONSP fixed-column-widths)
(SETF fixed-width-for-this-column (FIRST fixed-column-widths)))
(SETF width-for-this-column
(OR fixed-width-for-this-column (AREF column-widths column 0)))
(WHEN (AND (= row kids-row) (= column kids-column))
(SETF childs-horizontal-size (+ (org-entry-width org-entry)
(org-entry-border-width org-entry)
(org-entry-border-width org-entry))
childs-vertical-size (+ (org-entry-height org-entry)
(org-entry-border-width org-entry)
(org-entry-border-width org-entry)))
(IF (EQ same-width-in-column :on)
(SETF childs-horizontal-size width-for-this-column
x1 x)
;; else...
(SETF childs-horizontal-size (MIN childs-horizontal-size
width-for-this-column)
x1 (CASE column-alignment
(:left x)
(:right (+ x (- width-for-this-column
childs-horizontal-size)))
(:center (+ x (FLOOR (- width-for-this-column
childs-horizontal-size) 2))))))
(IF (EQ same-height-in-row :on)
(SETF childs-vertical-size height-for-this-row
y1 y)
;; else...
(SETF childs-vertical-size (MIN childs-vertical-size
height-for-this-row)
y1 (CASE row-alignment
(:top y)
(:bottom (+ y (- height-for-this-row
childs-vertical-size)))
(:center (+ y (FLOOR (- height-for-this-row
childs-vertical-size) 2))))))
;;
;; Reposition and/or resize the child iff needed...
;;
(LET ((desired-width (- childs-horizontal-size
(org-entry-border-width org-entry)
(org-entry-border-width org-entry)))
(desired-height (- childs-vertical-size
(org-entry-border-width org-entry)
(org-entry-border-width org-entry))))
(with-state (kid)
(UNLESS (AND (= x1 (contact-x kid))
(= y1 (contact-y kid)))
(move kid x1 y1))
(UNLESS (AND (= desired-width (contact-width kid))
(= desired-height (contact-height kid))
(= (org-entry-border-width org-entry)
(contact-border-width kid)))
(resize kid desired-width desired-height
(org-entry-border-width org-entry))))
;;
;; Done with this child, move on to the next...
;;
(SETF org-list (REST org-list))
(SETF last-kid-processed kid)))
;;
;; Whether or not a kid was placed at this row/column, move on to the
;; next column...
(INCF x (+ width-for-this-column
(display-horizontal-space table)))
(WHEN (CONSP fixed-column-widths)
(SETF fixed-column-widths (REST fixed-column-widths))))
;;
;; Get vertical position of top of borders of next row's elements...
;;
(INCF y (+ height-for-this-row
(display-vertical-space table)))
(WHEN (MEMBER row separators)
(INCF y (FLOOR (+ height-for-this-row
(display-vertical-space table)) 2))))))
))
;;
;; Having finished placing the kids we can put our preferred size into our wis...
;;
(SETF (what-if-preferred-height wis) (calculate-preferred-height table wis)
(what-if-preferred-width wis) (calculate-preferred-width table wis))
)))
(DEFUN scan-for-largest-children (wis)
(LET* ((max-child-heights-by-row (MAKE-ARRAY (what-if-nrows wis) :initial-element 0))
(max-child-widths-by-column (MAKE-ARRAY (what-if-ncolumns wis) :initial-element 0)))
(DOLIST (org-entry (REST (what-if-organization wis)))
(LET ((row (org-entry-row org-entry))
(column (org-entry-column org-entry))
(total-child-width (+ (org-entry-width org-entry)
(org-entry-border-width org-entry)
(org-entry-border-width org-entry)))
(total-child-height (+ (org-entry-height org-entry)
(org-entry-border-width org-entry)
(org-entry-border-width org-entry))))
(SETF (SVREF max-child-heights-by-row row)
(MAX (SVREF max-child-heights-by-row row) total-child-height))
(SETF (SVREF max-child-widths-by-column column)
(MAX (SVREF max-child-widths-by-column column) total-child-width))))
(VALUES max-child-heights-by-row max-child-widths-by-column)))
(DEFUN put-kids-into-specified-number-of-columns (table wis)
(DECLARE (VALUES widths-for-columns))
(with-slots (column-width columns children) (THE table table)
(LET* (fixed-width-for-this-column total-kid-width
(fixed-widths-for-columns column-width))
(SETF (what-if-ncolumns wis) columns
(what-if-nrows wis) (CEILING (LENGTH children) columns)
(what-if-column-widths wis) (MAKE-ARRAY `(,columns 2) :initial-element 0))
;; Construct the organization list by assigning the children to specific row/column
;; positions in the Table...
(assign-kids-to-rows-and-columns table wis)
;; Ncolumns was specified by the user. Nrows was determined from this and by
;; assign-kids-to-rows-and-columns. This routine scans the organization and builds the array
;; of (list column-width width-of-widest-entry-column) entries. This array is left in the
;; column-widths slot.
;;
;; Find the widest child in each row, set the 2nd element of each width-of-columns
;; entry to the width of the widest child in that column...
;;
(DO ((org-list1 (REST (what-if-organization wis)) (REST org-list1))
kid1 org-entry1 kid1s-column kid1s-row)
((NULL org-list1))
(SETF org-entry1 (FIRST org-list1))
(SETF kid1 (org-entry-kid org-entry1)
kid1s-row (org-entry-row org-entry1)
kid1s-column (org-entry-column org-entry1))
(SETF total-kid-width (+ (org-entry-width org-entry1)
(org-entry-border-width org-entry1)
(org-entry-border-width org-entry1)))
(Setf (AREF (what-if-column-widths wis) kid1s-column 1)
(MAX (AREF (what-if-column-widths wis) kid1s-column 1) total-kid-width)))
;;
;; Now go through the columns looking for those with pre-set widths. Use any pre-set
;; width as the column's width, otherwise use the width of the column's widest child.
;;
(SETF fixed-widths-for-columns column-width)
(DOTIMES (current-column (what-if-ncolumns wis))
;; Get current-column's fixed width, if any...
(SETF fixed-width-for-this-column
(TYPECASE fixed-widths-for-columns
(integer fixed-widths-for-columns)
(CONS (PROG1 (FIRST fixed-widths-for-columns)
(SETF fixed-widths-for-columns (REST fixed-widths-for-columns))))))
(SETF (AREF (what-if-column-widths wis) current-column 0)
(OR fixed-width-for-this-column (AREF (what-if-column-widths wis) current-column 1)))))))
(DEFUN find-first-parents-width (table)
(DO ((parent (contact-parent table) (contact-parent parent)))
((NULL parent))
(UNLESS (ZEROP (contact-width parent))
(RETURN (contact-width parent)))))
(DEFUN put-kids-into-maximum-unaligned-columns (table wis really-p)
(with-slots (children same-width-in-column) (THE table table)
(LET* ((org-list (LIST nil))
(working-width (what-if-width wis))
(border-width (what-if-border-width wis)))
(WHEN (ZEROP working-width)
(SETF working-width (- (find-first-parents-width table) border-width border-width)))
;; Start by sorting the list of children by their row/column constraints. Once this is
;; done we ignore the constraints from here on for :none layout policy...
(LET ((nkids (LENGTH children)))
(SETF (what-if-nrows wis) nkids
(what-if-ncolumns wis) nkids)
(assign-kids-to-rows-and-columns table wis))
(LET ((next-x-pos (display-left-margin table))
(next-y-pos (display-top-margin table))
(largest-height-this-row 0)
(columns-this-row 0)
(ncolumns-in-table 0)
(nrows-in-table 0)
(preferred-width-of-table 0))
(FLET
((handle-the-end-of-a-row ()
(SETF ncolumns-in-table (MAX ncolumns-in-table columns-this-row))
(SETF preferred-width-of-table
(MAX preferred-width-of-table
(+ next-x-pos
(- (display-right-margin table)
(display-horizontal-space table)))))
(SETF next-x-pos (display-left-margin table))
(INCF nrows-in-table)
(INCF next-y-pos (+ largest-height-this-row
(display-vertical-space table)))
(SETF columns-this-row 0
largest-height-this-row 0))
)
(DOLIST (child children)
(UNLESS (EQ (contact-state child) :withdrawn)
(MULTIPLE-VALUE-BIND (childs-p-width childs-p-height childs-p-border-width)
(preferred-size child)
(LET ((childs-total-width (+ childs-p-width (* 2 childs-p-border-width)))
(childs-total-height (+ childs-p-height (* 2 childs-p-border-width))))
;;
;; If cannot place this child at the end of this row, finish off this row and move
;; on to the next row...
;;
(WHEN (< (- working-width next-x-pos (display-right-margin table))
childs-total-width)
(handle-the-end-of-a-row))
;;
;; Position this child where we've decided it should go...
;;
(WHEN really-p
(with-state (child)
(UNLESS (AND (= next-x-pos (contact-x child))
(= next-y-pos (contact-y child)))
(move child next-x-pos next-y-pos))
(UNLESS (AND (= childs-p-width (contact-width child))
(= childs-p-height (contact-height child))
(= childs-p-border-width (contact-border-width child)))
(resize child childs-p-width childs-p-height childs-p-border-width))))
;;
;; Done with this child, move on to the next child and the next position in this
;; row...
;;
(PUSH (make-org-entry :kid child
:row nrows-in-table
:column columns-this-row
:width childs-p-width
:height childs-p-height
:border-width childs-p-border-width) org-list)
(INCF next-x-pos (+ childs-total-width
(display-horizontal-space table)))
(SETF largest-height-this-row (MAX largest-height-this-row childs-total-height))
(INCF columns-this-row)))))
;;
;; Set into the what-if structure the height, width, and organization just calculated...
;;
(handle-the-end-of-a-row)
(SETF (what-if-nrows wis) nrows-in-table)
(SETF (what-if-ncolumns wis) ncolumns-in-table)
(SETF (what-if-preferred-height wis)
(+ next-y-pos (- (display-vertical-space table))
(display-bottom-margin table)))
(SETF (what-if-preferred-width wis) preferred-width-of-table)
(SETF (what-if-organization wis) (NREVERSE org-list))
;;
;; Set up a fake column-widths array for others...
;;
(SETF (what-if-column-widths wis)
(MAKE-ARRAY `(,ncolumns-in-table 2) :initial-element 0))
(SETF (AREF (what-if-column-widths wis) 0 0) (what-if-preferred-width wis)))))))
(DEFUN put-kids-into-maximum-aligned-columns (table wis)
;; This is a guessing procedure that implements the XtNmaximumColumns policy for row and column
;; layout. Keep an array of items (column-width max-width-of-columns-items). Create and
;; initialize it from the 1st child: identical column widths = 1st child's preferred width,
;; max-width-of-columns-items = 0. Set NROWS to 0. Then start trying to place the children
;; into these columns. The 1st child will fit for sure, updating the 1st column's max-width.
;; The 2nd-Nth children may or may not fit. If it does, update max-width. If not, see if
;; other columns' can be made narrower to allow this column to be made wide enough for him to
;; fit. If so, do it. If not, we must reduce the number of columns by one, assigning them
;; equal widths, then start the layout process from the top. Each time we try to place a child
;; in the first column, increment NROWS.
;; Note that while this routine tends to give about the same amount of space to each column,
;; the slack space for the columns may differ considerably. After we find a child the cannot
;; fit in a column and reduce the number of columns to get more space, we give each column the
;; same, new, enlarged space. If one column is actually fairly narrow and doesn't need more
;; space it'll end up with extra slack space around it. A slack-space-smoothing routine should
;; be written to improve this.
(DECLARE (VALUES nrows ncolumns column-widths))
(with-slots (children column-width) (THE table table)
(LET ((nkids (LENGTH children))
(working-width (what-if-width wis))
(working-border-width (what-if-border-width wis)))
(WHEN (<= working-width 0)
(SETF working-width (- (find-first-parents-width table)
working-border-width working-border-width)))
;;
;; Start by sorting the list of children by their row/column constraints. Once this is
;; done we ignore the constraints from here on for :maximum layout policy...
;;
(SETF (what-if-nrows wis) nkids
(what-if-ncolumns wis) nkids)
(assign-kids-to-rows-and-columns table wis)
;; Start with an upper bound on the number of columns...
(LET* ((ncolumns (MIN nkids (get-maximum-possible-ncolumns table working-width)))
(column-widths (MAKE-ARRAY `(,ncolumns 2)))
(column-widths-vector (MAKE-ARRAY (* 2 ncolumns) :displaced-to column-widths)))
;;
;; Each execution of this outer loop represents an attempt at fitting the children
;; into a given number of columns. The inner loop below does the actual laying out of
;; the children; if it succeeds, it sets FINISHED to T as it exits. If it fails, it
;; decrements NCOLUMNS and leaves FINISHED NIL.
;;
(DO* (finished
(org-list (LIST nil))
(org-tail org-list)
next-row next-column)
(finished
;;
;; Make each column's real width equal to the widest child we've placed in it,
;; adjust ncolumns by the number of unused columns...
;;
(DOTIMES (column ncolumns)
(IF (ZEROP (AREF column-widths column 1))
(DECF ncolumns)
(SETF (AREF column-widths column 0) (AREF column-widths column 1))))
(SETF (what-if-column-widths wis) column-widths)
(SETF (what-if-ncolumns wis) ncolumns)
(SETF (what-if-organization wis) org-list)
(SETF (what-if-nrows wis) (1+ next-row)))
;; Initialize the first ncolumns elements of the column-widths array...
;; Total horizontal space available for the columns:
;; width - right-margin - left-margin - (n - 1)*horizontal-space.
;; This total is divided into ncolumns equal chunks, with any extra white space
;; being given a pixel at a time to the left-most columns.
;; But not quite. We need to handle fixed-width columns specially. At this point
;; we know how many columns we're (tentatively) giving the table, call it N. We
;; need to see how much of our space is occupied by fixed-width columns in the
;; first N columns and how many there are, call it M. The remaining N-M columns
;; each gets 1/(N-M) of the remaining space. Be careful abaout N=M! And each
;; fixed-width column gets *both* of its column-width entries initialized here to
;; its fixed width so it'll look like there's no slack in that column (which there
;; isn't). Unlike a variable-width column, a fixed-width column never gets its
;; 2nd column-widths entry changed as we place kids in it.
(LET ((total-fixed-width 0) (n-fixed-width-columns 0)
(fixed-column-widths (UNLESS (EQ column-width :maximum) column-width)))
;; Forget the column widths calculated last time through the loop...
(FILL (THE vector column-widths-vector) nil)
;; Calculate how much of the total table width is allocated to fixed-width
;; columns...
(COND
((NULL fixed-column-widths))
((INTEGERP fixed-column-widths)
(SETF total-fixed-width (* ncolumns fixed-column-widths)
n-fixed-width-columns ncolumns)
(DOTIMES (column-number ncolumns)
(SETF (AREF column-widths column-number 0)
(SETF (AREF column-widths column-number 1) fixed-column-widths))))
((CONSP fixed-column-widths)
(DO ((fixed-column-widths fixed-column-widths (REST fixed-column-widths))
(column-number 0 (1+ column-number))
fixed-width)
((OR (= column-number ncolumns)
(ENDP fixed-column-widths)))
(SETF fixed-width (FIRST fixed-column-widths))
(WHEN fixed-width
(INCF n-fixed-width-columns)
(INCF total-fixed-width fixed-width)
(SETF (AREF column-widths column-number 0)
(SETF (AREF column-widths column-number 1) fixed-width)))))
(t (ERROR "column-width is ~a." fixed-column-widths)))
;; Now n-fixed-width-columns = # of fixed width columns in first ncolumns
;; total-fixed-width = # of pixels occupied by those columns
;; and for each fixed-width column both column-widths entries = the fixed width.
;; Take the remaining space and give it to the non-fixed-width columns...
(UNLESS (ZEROP (- ncolumns n-fixed-width-columns))
(MULTIPLE-VALUE-BIND (horizontal-space-for-each-var-column extra-white-space)
(FLOOR (- working-width
(display-left-margin table)
(display-right-margin table)
(* (1- ncolumns) (display-horizontal-space table))
total-fixed-width)
(- ncolumns n-fixed-width-columns))
;; Assign the non-fixed-width space to the non-fixed-width columns. Because
;; we FILL column-widths with NIL each time through the main loop, only
;; fixed-width columns will have none-NIL values in them. Give the extra
;; white-space to the left-most variable-width columns a pixel at a time.
(DOTIMES (i ncolumns)
(WHEN (NULL (AREF column-widths i 0))
(SETF (AREF column-widths i 0)
(+ horizontal-space-for-each-var-column
(IF (ZEROP extra-white-space)
0
(PROGN (DECF extra-white-space) 1))))
(SETF (AREF column-widths i 1) 0)))))
(SETF org-list (LIST nil)
org-tail org-list
next-row -1
next-column (1- ncolumns))
;;
;; Try to lay the children into the columns sized as they are now...
;;
(DOLIST (child children (SETF finished t))
(UNLESS (EQ (contact-state child) :withdrawn)
;;
;; If the column this child's to go in is beyond ncolumns, wrap to the first
;; column of the next row...
;;
(INCF next-column)
(WHEN (= next-column ncolumns)
(SETF next-column 0)
(INCF next-row)
(SETF fixed-column-widths (UNLESS (EQ column-width :maximum) column-width)))
(LET* ((columns-width-right-now (AREF column-widths next-column 0))
(fixed-width-for-this-column
(IF (LISTP fixed-column-widths) ;; ERCM
(FIRST fixed-column-widths)
fixed-column-widths)))
(UNLESS fixed-width-for-this-column
;; Find out what width the child thinks he should be...
(MULTIPLE-VALUE-BIND (childs-width childs-height childs-border-width)
(preferred-size child :width columns-width-right-now)
(DECLARE (IGNORE childs-height))
;; Calculate how much horizontal space this child needs...
(LET ((horizontal-space-for-this-child
(+ childs-width childs-border-width childs-border-width)))
(COND
((OR (<= horizontal-space-for-this-child columns-width-right-now)
(adjust-column-widths-so-child-fits
column-widths horizontal-space-for-this-child
next-column ncolumns))
(SETF (AREF column-widths next-column 1)
(MAX (AREF column-widths next-column 1)
horizontal-space-for-this-child)))
(t
;; else child can't fit in this column. Reduce the number of
;; columns and try again.
(DECF ncolumns)
(RETURN nil)))))))
;; To get here we must have decided we can successfully place this kid at
;; this position, so add an entry for it onto the org-list...
(SETF (REST org-tail)
(LIST (establish-org-entry child next-row next-column)))
(SETF org-tail (REST org-tail))
;; Advance to the next column's entry in the fixed-width list if there is
;; one...
(WHEN (CONSP fixed-column-widths)
(SETF fixed-column-widths (REST fixed-column-widths)))))))))))
(DEFUN adjust-column-widths-so-child-fits (column-widths childs-width next-column ncolumns)
(DO ((npixels-needed (- childs-width (AREF column-widths next-column 0))))
((ZEROP npixels-needed)
(SETF (AREF column-widths next-column 0) childs-width)
t)
;; Find column with greatest slack, if any...
(LET ((max-slack 0) (max-slack-col nil))
(DOTIMES (col ncolumns)
(UNLESS (= next-column col) ; Don't look at column child goes in
(LET ((slack (- (AREF column-widths col 0) (AREF column-widths col 1))))
(WHEN (> slack max-slack)
(SETF max-slack slack
max-slack-col col)))))
;; If no column had any slack, return NIL...
(UNLESS max-slack-col (RETURN nil))
;; Otherwise take a pixel from the max-slack-col's width, reduce our goal by one, try
;; again...
(DECF (AREF column-widths max-slack-col 0))
(DECF npixels-needed))))
(DEFUN get-maximum-possible-ncolumns (table width)
"Returns the maximum number of columns possible given the specified constraints."
(with-slots (children column-width) (THE table table)
(LET* ((fixed-column-widths (UNLESS (EQ column-width :maximum) column-width))
(minimum-column-width
(- width (display-left-margin table) (display-right-margin table))))
;;
;; If the caller specified a single fixed width for all columns, then that's it...
;;
(IF (INTEGERP fixed-column-widths)
(SETF minimum-column-width (MIN minimum-column-width fixed-column-widths))
;; else...
(PROGN
;;
;; If the caller specified a list of fixed widths (and nil's) for (some of) the
;; columns, first find the minimum of these fixed column widths...
;;
(WHEN (CONSP fixed-column-widths)
(DOLIST (this-fixed-column-width fixed-column-widths)
(WHEN this-fixed-column-width
(SETF minimum-column-width
(MIN minimum-column-width this-fixed-column-width)))))
;;
;; Then as a crude approximation, find the narrowest child, not knowing what column
;; the child will go in...
;;
(DOLIST (kid children)
(UNLESS (EQ (contact-state kid) :withdrawn)
(MULTIPLE-VALUE-BIND (preferred-width preferred-height preferred-border-width)
(preferred-size kid)
(DECLARE (IGNORE preferred-height))
(SETF minimum-column-width
(MIN minimum-column-width
(+ preferred-width preferred-border-width preferred-border-width))))))))
;; Now that we have the smallest column width we could ever get, calculate and return the
;; maximum number of columns we could ever have...
(MIN (LENGTH children)
(FLOOR (+ (- width
(display-left-margin table)
(display-right-margin table))
(display-horizontal-space table))
(+ minimum-column-width (display-horizontal-space table)))))))
;;;
;;; These routines construct the ORGANIZATION list by placing each child at a specific
;;; row/column position
;;;
;;;. Lexical variables:
;;; hole-pointer where in the existing organization list to rplacd-in an entry for an
;;; unconstrained child -- the current "hole". All entries in the
;;; organization list preceding this one are contiguous starting from row 0,
;;; column 0, so all attempts at child placement, regardless of the
;;; constraints, start from here. Hole-row & hole-column are one row/col
;;; position beyond the row/col of (FIRST hole-pointer), unless (first
;;; hole-pointer) is NIL, in which case they are (0,0).
;;; hole-row the row-number of the current hole.
;;; hole-column the column-number of the current hole.
;;; ncolumns the number of columns in the table. Fixed.
;;; nrows the number of rows in the table. Can change if a child specifies a big
;;; row-constraint.
;;;
(DEFUN assign-kids-to-rows-and-columns (table wis)
(LET (hole-pointer hole-row hole-column ncolumns nrows)
(DECLARE (inline insert-into-organization-list))
(LABELS
(
;;
;; Makes sure the hole-pointer/row/column actually point at a hole. If they currently
;; point at an allocated table row/column, moves them over until they point at an
;; unallocated one.
;;
(find-next-hole
()
(DO* (org-entry org-row org-column
(org-list hole-pointer))
(nil)
;;
;; Look at the next org-entry, the one just beyond the hole pointer. The second -
;; Nth times through the loop this also advances the hole-pointer...
;;
(SETF hole-pointer org-list
org-list (REST org-list))
(WHEN org-list
(SETF org-entry (FIRST org-list)
org-row (org-entry-row org-entry)
org-column (org-entry-column org-entry)))
(WHEN (OR (NULL org-list) ; Exhausted org-list. Leave hole pointing at
; row/col one beyond the last org-entry.
(/= org-row hole-row) ; There's space between the previous org-entry
(/= org-column hole-column)) ; and this one. Leave hole pointing
; at row/col one beyond the previous
; org-entry.
(RETURN))
;;
;; The row/column position of the hole is occupied. Move the row/column of the hole
;; over one position, try again...
;;
(WHEN (= (INCF hole-column) ncolumns)
(INCF hole-row)
(SETF hole-column 0))))
;;
;; Insert KID into the organization list at INSERTION-POINT at ROW/COLUMN...
;;
(insert-into-organization-list
(kid insertion-point row column)
(RPLACD insertion-point
(CONS (establish-org-entry kid row column)
(REST insertion-point)))
(find-next-hole)
(WHEN (>= row nrows) ; Update nrows if necessary.
(SETF nrows (1+ row)))) ; *
;;
;; Inserts a kid with no constraints in the next hole, moves the hole pointers. Always
;; successful, so always returns T.
;;
(place-a-kid-at-any-row-and-column
(kid)
(insert-into-organization-list kid hole-pointer hole-row hole-column)
t)
;;
;; Tries to insert a kid into a specific row/column, returning T if successful, NIL if
;; not. Fails if that row/column is already occupied or specified column is outside
;; ncolumns.
;;
(place-a-kid-at-a-specific-row-and-column
(kid kid-row kid-column)
(LET ((kid-position (+ (* ncolumns kid-row) kid-column))
(last-occupied-position
(IF (FIRST hole-pointer)
(+ (* ncolumns (org-entry-row (FIRST hole-pointer)))
(org-entry-column (FIRST hole-pointer)))
-1)))
(WHEN (OR (>= kid-column ncolumns)
(>= last-occupied-position kid-position))
(RETURN-FROM place-a-kid-at-a-specific-row-and-column nil))
(DO ((org-list hole-pointer) insertion-point org-position)
(nil)
(SETF insertion-point org-list
org-list (REST org-list))
(SETF org-position
(IF org-list
(+ (* ncolumns (org-entry-row (FIRST org-list)))
(org-entry-column (FIRST org-list)))
(1+ kid-position)))
(COND
((= org-position kid-position) ; Kid's row/column occupied: failure.
(RETURN-FROM place-a-kid-at-a-specific-row-and-column nil))
((> org-position kid-position) ; Kid's row/column free: success.
(insert-into-organization-list kid insertion-point kid-row kid-column)
(RETURN-FROM place-a-kid-at-a-specific-row-and-column t))
(t nil)))))
;;
;; Tries to insert a kid into a specific row.
;; Fails if row is full, returns NIL, otherwise is successful, returns T.
;;
(place-a-kid-in-a-specific-row
(kid kid-row)
(WHEN (< kid-row hole-row)
(RETURN-FROM place-a-kid-in-a-specific-row nil))
(DO ((org-list hole-pointer) insertion-point
(last-occupied-column
(IF (FIRST hole-pointer) (org-entry-column (FIRST hole-pointer)) -1) org-column)
org-entry (org-row kid-row) org-column)
((OR (NULL org-list)
(> org-row kid-row))
;; Failure -- exit here iff couldn't insert child
nil)
(SETF insertion-point org-list
org-list (REST org-list))
(IF org-list
(SETF org-entry (FIRST org-list)
org-row (org-entry-row org-entry)
org-column (org-entry-column org-entry))
;; else no more org-entries so fake one way out there...
(SETF org-row (1+ kid-row)))
(WHEN (OR (AND (= org-row kid-row) ; In kid's row and there's a hole.
(< (1+ last-occupied-column) ; *
org-column)) ; *
(AND (> org-row kid-row) ; First org-entry beyond kid's row
(< last-occupied-column ; and there's a hole at the end
(1- ncolumns)))) ; of the kid's row.
(insert-into-organization-list
kid insertion-point kid-row (1+ last-occupied-column))
(RETURN-FROM place-a-kid-in-a-specific-row t))))
;;
;; Inserts a kid into a specific column.
;; Fails if column is not within ncolumns, returns NIL, otherwise always successful,
;; returns T.
;;
(place-a-kid-in-a-specific-column
(kid kids-column)
(WHEN (>= kids-column ncolumns)
(RETURN-FROM place-a-kid-in-a-specific-column nil))
(DO* ((org-list hole-pointer) insertion-point
(last-org-position -1 org-position) org-position
(insertion-row (IF (< kids-column hole-column) (1+ hole-row) hole-row))
(position-of-next-occurrence-of-kids-column
(+ (* ncolumns insertion-row) kids-column)))
(nil)
(SETF insertion-point org-list
org-list (REST org-list))
(SETF org-position
(IF org-list
(+ (* ncolumns (org-entry-row (FIRST org-list)))
(org-entry-column (FIRST org-list)))
(1+ position-of-next-occurrence-of-kids-column)))
(WHEN (< last-org-position
position-of-next-occurrence-of-kids-column
org-position)
(insert-into-organization-list kid insertion-point insertion-row kids-column)
(RETURN-FROM place-a-kid-in-a-specific-column t))
;; Calculate a new position-of-next-occurrence-of-kids-column if this org-entry is at
;; or beyond the current value...
(WHEN (>= org-position position-of-next-occurrence-of-kids-column)
(INCF position-of-next-occurrence-of-kids-column ncolumns)
(INCF insertion-row))))
;;
;; This is called by assign-kids-to-rows-and-columns when it realizes it is dealing with
;; a :maximum or :none table. The Table's children list is rebuilt to be
;; the (already sorted) kids in the org-list followed by the kids in the free-list.
;; Where unconstrained kids would normally be used to fill in holes in a
;; fixed-number-of-columns table, there really are no holes for a :maximum or
;; :none table so such children are just placed at the end of the Table's
;; children list.
;;
(build-sorted-list-of-children
(table org-list free-list withdrawn-children)
(with-slots (children) (THE table table)
(LET* ((sorted-children-list (MAKE-LIST (LENGTH org-list))) ; includes leading NIL.
(next-sorted-children-list sorted-children-list)
(last-sorted-children-list sorted-children-list))
(DOLIST (org-entry (REST org-list))
(SETF last-sorted-children-list next-sorted-children-list
next-sorted-children-list (REST next-sorted-children-list))
(RPLACA next-sorted-children-list (org-entry-kid org-entry)))
(WHEN free-list
(RPLACD last-sorted-children-list (NCONC free-list withdrawn-children)))
(SETF children (REST sorted-children-list)))))
) ; ...end of labels...
;; ====================================================================================
;; The code for assign-kids-to-rows-and-columns (table wis):
;; Constructs the what-if-organization list by assigning each kid to a specific
;; row/column position in the table.
;;
(with-slots (children) (THE table table)
(LET (free-row free-col free (old-org-list (REST (what-if-organization wis)))
withdrawn-children)
(SETF (what-if-organization wis) (LIST nil)
hole-pointer (what-if-organization wis)
hole-row 0
hole-column 0
ncolumns (what-if-ncolumns wis)
nrows (what-if-nrows wis))
;; First try to place all the kids with definite row/column constraints.
;; Any child specifying only a row goes on the free-col list.
;; Any child specifying only a column goes on the free-row list.
;; Any child specifying neither row nor column, or any child unable to be placed where
;; its definite row/column constraints placed it, goes on the free list.
(DOLIST (kid children)
(COND
((NOT (EQ (contact-state kid) :withdrawn))
(UNLESS (OR (NULL old-org-list)
(EQ kid (org-entry-kid (FIRST old-org-list))))
(CERROR "continue" "children and org-list don't match"))
(LET ((row (OR (table-row kid)
(AND old-org-list (org-entry-row (FIRST old-org-list)))))
(column (OR (table-column kid)
(AND old-org-list (org-entry-column (FIRST old-org-list))))))
(SETF old-org-list (REST old-org-list))
(COND
((AND row column)
(UNLESS (place-a-kid-at-a-specific-row-and-column kid row column)
(PUSH kid free)))
(row
(PUSH `(,kid ,row) free-col))
(column
(PUSH `(,kid ,column) free-row))
(t
(PUSH kid free)))))
(t
(PUSH kid withdrawn-children))))
;; Now try to place all the kids specifying only a column. Since it is always OK to
;; create a new row, such kids can always be placed...
(DOLIST (kid-and-column (NREVERSE free-row))
(place-a-kid-in-a-specific-column (FIRST kid-and-column) (SECOND kid-and-column)))
;; Now try to place all the kids specifying only a row. If that row is full, place
;; the child on the free list...
(DOLIST (kid-and-row (NREVERSE free-col))
(UNLESS (place-a-kid-in-a-specific-row (FIRST kid-and-row) (SECOND kid-and-row))
(PUSH (FIRST kid-and-row) free)))
;; Finally, place the kids that are on the free list. These kids have no constraints,
;; so they'll all be placed in holes scanning from top-left to bottom-right or new
;; rows will be created to hold them...
(IF (SYMBOLP (table-columns table))
(build-sorted-list-of-children
table (what-if-organization wis) (NREVERSE free) withdrawn-children)
;; else...
(PROGN
(DOLIST (kid (NREVERSE free))
(place-a-kid-at-any-row-and-column kid))
;;
;; Rebuild the children list in the order of the what-if-organization
;; followed by any :withdrawn children not on the what-if-organization list.
;;
(DO ((children children (REST children))
(organization (REST (what-if-organization wis)) (REST organization)))
((NULL organization)
(DOLIST (withdrawn-child withdrawn-children)
(RPLACA children withdrawn-child)
(SETF children (REST children))))
(RPLACA children (org-entry-kid (FIRST organization))))))
(SETF (what-if-nrows wis) nrows))))))
;; This is called by assign-kids-to-rows-and-columns when it realizes it is dealing with a
;; :maximum or :none table. The Table's children list is rebuilt to be the
;; (already sorted) kids in the org-list followed by the kids in the free-list. Where
;; unconstrained kids would normally be used to fill in holes in a fixed-number-of-columns
;; table, there really are no holes for a :maximum or :none table so such children
;; are just placed at the end of the Table's children list.
(DEFUN build-sorted-list-of-children (table org-list free-list withdrawn-children)
(with-slots (children) (THE table table)
(LET* ((sorted-children-list (MAKE-LIST (LENGTH org-list))) ; includes leading NIL.
(next-sorted-children-list sorted-children-list)
(last-sorted-children-list sorted-children-list))
(DOLIST (org-entry (REST org-list))
(SETF last-sorted-children-list next-sorted-children-list
next-sorted-children-list (REST next-sorted-children-list))
(RPLACA next-sorted-children-list (org-entry-kid org-entry)))
(WHEN free-list
(RPLACD last-sorted-children-list (NCONC free-list withdrawn-children)))
(SETF children (REST sorted-children-list)))))